﻿using System.Collections.Generic;
using System.Text.RegularExpressions;
using UnityEngine.EventSystems;
using System;
using System.Text;

namespace UnityEngine.UI
{

    public static class ToStringUtil
    {
        public static string ToStr(this Vector3 v)
        {
            return $"({v.x}, {v.y}, {v.z})";
        }

        public static string ToStr(this Vector2 v)
        {
            return $"({v.x}, {v.y})";
        }
    }

    /*
     * 图片下对齐 <icon name=1 align=bottom event=click_pic args=2/>
     *图片上对齐 <icon name=1 align=top event=click_pic args=3/>

图片缩放 <icon name=2 w=0.7 h=0.7 event=click_pic args=4/>

<material=underline event=click_underline args=1>下划线点击响应</material>

<material=shadow>阴影效果</material>

<material=outline>描边效果</material>

<material=gradient from=#ff0000 to=#00ff00 x=1 y=0>颜色渐变效果</material>

<material=underline event=click_underline  args=2><material=shadow><color=#ff0000>支持<size=16>各种标签混合使用</size></color></material></material>
*/


    /// <summary>
    /// 图片<icon name=??? w=1 h=1 align=top/center/bottom event=*** args=***/> 图片name和sprite需要通过调用AddSprite方法设置
    /// 阴影<material=shadow c=#000000 x=1 y=-1>blablabla...</material>
    /// 描边<material=outline c=#000000 x=1 y=-1>blablabla...</material>
    /// 渐变<material=gradient from=#FFFFFF to=#000000 x=0 y=-1>blablabla...</material>
    /// 下划线<material=underline c=#FFFFFF h=1.5 event=*** args=***>blablabla...</material>
    /// 只有下划线会受到Text.color的影响
    /// 支持Unity RichText标签: color, size, b, i
    /// 标签可嵌套
    /// unity会忽略material对排版的影响，这里全部使用material标签以简化标签检测
    /// </summary>
    [ExecuteInEditMode]
    public class RichText : Text, IPointerClickHandler
    {
        private FontData fontData;

        private UIVertex[] tempVerts;

        private TextInterpreter textInterpreter = new TextInterpreter();

        private Action<string, string> clickHandler = delegate { };

        // --------------- Icon ---------------- //

        private static readonly Regex IconReg = new Regex(@"<icon name=([^>\s]+)([^>]*)/>");
        private static readonly Regex ItemReg = new Regex(@"(\w+)=([^\s]+)");

        private const char ReplaceChar = ' ';



        [System.Serializable]
        public struct SpriteName
        {
            public string name;
            public Sprite sprite;
        }

        private class IconInfo
        {
            public string name;
            public bool still;
            public Color color;
            public Vector2 anchoredPos;
            public Vector2 size;
            public int vertStart;
            public int vertNum;
            public string e;
            public string args;
            public string align;
        }



        public SpriteName[] inspectorSpriteList;

        private Dictionary<string, Sprite> m_SpriteList = new Dictionary<string, Sprite>();

        private List<Image> m_ImageList = new List<Image>();

        private List<IconInfo> m_Icons = new List<IconInfo>();

        private bool m_IconLayoutDirty = true;

        // --------------- Event -------------- //

        private class Event
        {
            public Rect rect;
            public string name;
            public string args;
        }

        private List<Event> m_EventList = new List<Event>();

        protected RichText()
        {
            fontData = typeof(Text).GetField("m_FontData", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).GetValue(this) as FontData;
            tempVerts = typeof(Text).GetField("m_TempVerts", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).GetValue(this) as UIVertex[];
        }

        protected override void OnEnable()
        {
            base.OnEnable();
            UpdateSpriteList();
        }

        protected override void OnDestroy()
        {
            base.OnDestroy();
            ClearSprite();
            m_ImageList = null;
            inspectorSpriteList = null;
            m_SpriteList = null;
            clickHandler = null;
        }





        public void UpdateSpriteList()
        {
            m_SpriteList.Clear();
            if (inspectorSpriteList != null && inspectorSpriteList.Length > 0)
            {
                foreach (SpriteName icon in inspectorSpriteList)
                {
                    m_SpriteList[icon.name] = icon.sprite;
                }
            }
        }

        public void AddSprite(string name, Sprite sprite)
        {
            List<SpriteName> list = new List<SpriteName>(inspectorSpriteList);
            list.RemoveAll((SpriteName each) => each.name == name);
            list.Add(new SpriteName() { name = name, sprite = sprite });
            inspectorSpriteList = list.ToArray();
            m_SpriteList[name] = sprite;
        }

        public void ClearSprite()
        {
            foreach (Image image in m_ImageList)
            {
                if (image) image.sprite = null;
            }
            m_ImageList.Clear();
            m_SpriteList.Clear();
            inspectorSpriteList = null;
        }




        public void AddListener(Action<string, string> callBack)
        {
            clickHandler += callBack;
        }

        public void RemoveListener(Action<string, string> callback)
        {
            clickHandler -= callback;
        }

        public void RemoveAllListeners()
        {
            clickHandler = delegate { };
        }



#if UNITY_2017 || UNITY_5
        protected override void OnPopulateMesh(VertexHelper toFill)
        {
            if (font == null)
                return;

            // We don't care if we the font Texture changes while we are doing our Update.
            // The end result of cachedTextGenerator will be valid for this instance.
            // Otherwise we can get issues like Case 619238.
            m_DisableFontTextureRebuiltCallback = true;

            string richText = text;
            IList<UIVertex> verts = null;

            m_EventList.Clear();

            // Caculate layout
            try
            {
                richText = CalculateLayoutWithImage(richText, out verts);
            }
            catch (Exception e)
            {
                Debug.LogWarning(e.ToString());
                return;
            }

            // Last 4 verts are always a new line...
            int vertCount = verts.Count;
            for (int i = 0; i < 4; i++)
            {
                if (vertCount > 0)
                {
                    verts.RemoveAt(vertCount - 1);
                    vertCount -= 1;
                }
            }

            // Parse color tag
            List<Tag> tags = null;
            textInterpreter.Parse(richText, out tags);

            // Apply tag effect
            if (tags.Count > 0)
            {
                List<UIVertex> vertexs = verts as List<UIVertex>;
                if (vertexs != null)
                {
                    int capacity = 0;
                    for (int i = 0; i < tags.Count; i++)
                    {
                        Tag tag = tags[i];
                        switch (tag.type)
                        {
                        case TagType.Shadow:
                        {
                            capacity += (tag.tagContentEndIndex - tag.tagContentStartIndex) * 4;
                            break;
                        }
                        case TagType.Outline: //自身, lb, lt, rt, tb绘制5遍
                        {
                            capacity += (tag.tagContentEndIndex - tag.tagContentStartIndex) * 4 * 5;
                            break;
                        }
                        }
                    }
                    if (capacity > 0)
                    {
                        capacity = Mathf.Max(capacity, 16);
                        vertexs.Capacity += capacity;
                    }
                }
                for (int i = 0; i < tags.Count; i++)
                {
                    Tag tag = tags[i];
                    try
                    {
                        switch (tag.type)
                        {
                        case TagType.Shadow:
                        {
                            ApplyShadowEffect(tag as Shadow, verts);
                            break;
                        }
                        case TagType.Outline:
                        {
                            ApplyOutlineEffect(tag as Outline, verts);
                            break;
                        }
                        case TagType.Gradient:
                        {
                            ApplyGradientEffect(tag as GradientL, verts);
                            break;
                        }
                        case TagType.Underline:
                        {
                            ApplyUnderlineEffect(tag as Underline, verts);
                            break;
                        }
                        }
                    }
                    catch (Exception e)
                    {
                        Debug.LogWarning(e.ToString());
                        return;
                    }
                }
            }

            vertCount = verts.Count;

            float unitsPerPixel = 1 / pixelsPerUnit;

            Rect inputRect = rectTransform.rect;

            // get the text alignment anchor point for the text in local space
            Vector2 textAnchorPivot = GetTextAnchorPivot(fontData.alignment);
            Vector2 refPoint = Vector2.zero;
            refPoint.x = (textAnchorPivot.x == 1 ? inputRect.xMax : inputRect.xMin);
            refPoint.y = (textAnchorPivot.y == 0 ? inputRect.yMin : inputRect.yMax);

            // Determine fraction of pixel to offset text mesh.
            Vector2 roundingOffset = PixelAdjustPoint(refPoint) - refPoint;

            toFill.Clear();
            if (roundingOffset != Vector2.zero)
            {
                for (int i = 0; i < vertCount; ++i)
                {
                    int tempVertsIndex = i & 3;
                    tempVerts[tempVertsIndex] = verts[i];
                    tempVerts[tempVertsIndex].position *= unitsPerPixel;
                    tempVerts[tempVertsIndex].position.x += roundingOffset.x;
                    tempVerts[tempVertsIndex].position.y += roundingOffset.y;
                    if (tempVertsIndex == 3)
                        toFill.AddUIVertexQuad(tempVerts);
                }
            }
            else
            {
                for (int i = 0; i < vertCount; ++i)
                {
                    int tempVertsIndex = i & 3;
                    tempVerts[tempVertsIndex] = verts[i];
                    tempVerts[tempVertsIndex].position *= unitsPerPixel;
                    if (tempVertsIndex == 3)
                        toFill.AddUIVertexQuad(tempVerts);
                }
            }
            m_DisableFontTextureRebuiltCallback = false;
        }
#endif



        static void PrintTextGenInfo(string text, TextGenerator textGen)
        {
            Debug.Log($"========== TextGenInfo");

            var chs = textGen.characters;
            Debug.Log($"charCount:{chs.Count}, {textGen.characterCount}, visible:{textGen.characterCountVisible}");
            for (int i = 0; i < chs.Count; ++i)
            {
                var ch = chs[i];
                if (i < text.Length)
                    Debug.Log($"ch_{i}: '{text[i]}', w:{ch.charWidth}, cursoPos:{ch.cursorPos.ToStr()}");
                else
                    Debug.Log($"ch_{i}: w:{ch.charWidth}, cursoPos:{ch.cursorPos.ToStr()}");
            }

            //cursorPos和verts_lt的坐标区别是: cursorPos不会超出Text限定框、不会字符重叠, y方向是贴着lineTop,
            var verts = textGen.verts;
            Debug.Log($"vertCount:{verts.Count}, {textGen.vertexCount}");
            for (var i = 0; i < verts.Count; i += 4)
            {
                var lt = verts[i].position;
                var rt = verts[i + 1].position;
                var rb = verts[i + 2].position;
                var rl = verts[i + 3].position;
                Debug.Log($"quad_{i / 4}: lt:{lt.ToStr()}, rt:{rt.ToStr()}, rb:{rb.ToStr()}, rl:{rl.ToStr()}");
            }

            var lines = textGen.lines;
            Debug.Log($"lineCount:{lines.Count}, {textGen.lineCount}");
            float lastLineBottomY = 0;
            for (int i = 0; i < lines.Count; ++i)
            {
                var l = lines[i];
                float bottomY = l.topY - l.height;
                if (i > 0)
                {
                    float leading = l.topY - lastLineBottomY;
                    Debug.Log($"line_{i}: charIndex:{l.startCharIdx}, lineLeading:{l.leading}_{leading}, lineHeight:{l.height}, line_topY:{l.topY}, line_bottomY:{bottomY}");
                }
                else
                {
                    Debug.Log($"line_{i}: charIndex:{l.startCharIdx}, lineLeading:{l.leading}, lineHeight:{l.height}, line_topY:{l.topY}, line_bottomY:{bottomY}");
                }
                lastLineBottomY = bottomY;
            }

            Debug.Log($"==========");
        }

        protected string CalculateLayoutWithImage(string richText, out IList<UIVertex> textGenVerts)
        {
            Vector2 extents = rectTransform.rect.size;
            var settings = GetGenerationSettings(extents);

            float unitsPerPixel = 1 / pixelsPerUnit;

            float spaceWidth = cachedTextGenerator.GetPreferredWidth(ReplaceChar.ToString(), settings) * unitsPerPixel;

            float fontSize2 = fontSize * 0.5f;

            // Image replace
            m_Icons.Clear();
            Match match = null;
            StringBuilder builder = new StringBuilder();
            while ((match = IconReg.Match(richText)).Success)
            {
                IconInfo iconInfo = new IconInfo();
                iconInfo.name = match.Groups[1].Value;
                iconInfo.size = new Vector2(fontSize2, fontSize2);
                float w = 1, h = 1;
                string e = null, args = null, align = "center";
                string vars = match.Groups[2].Value;
                if (!string.IsNullOrEmpty(vars))
                {
                    Match itemMatch = ItemReg.Match(vars);
                    while (itemMatch.Success)
                    {
                        string name = itemMatch.Groups[1].Value;
                        string value = itemMatch.Groups[2].Value;
                        switch (name)
                        {
                        case "w":
                        {
                            float.TryParse(value, out w);
                            break;
                        }
                        case "h":
                        {
                            float.TryParse(value, out h);
                            break;
                        }
                        case "event":
                        {
                            e = value;
                            break;
                        }
                        case "args":
                        {
                            args = value;
                            break;
                        }
                        case "align":
                        {
                            align = value;
                            break;
                        }
                        }
                        itemMatch = itemMatch.NextMatch();
                    }
                }
                if (m_SpriteList.ContainsKey(iconInfo.name))
                {
                    Sprite sprite = m_SpriteList[iconInfo.name];
                    if (sprite != null)
                    {
                        iconInfo.size = new Vector2(sprite.rect.width * w, sprite.rect.height * h);
                    }
                }
                iconInfo.e = e;
                iconInfo.args = args;
                iconInfo.align = align;
                iconInfo.vertStart = match.Index * 4;
                int spaceNum = Mathf.CeilToInt(iconInfo.size.x / spaceWidth);
                iconInfo.vertNum = spaceNum * 4;
                m_Icons.Add(iconInfo);

                builder.Length = 0;
                builder.Append(richText, 0, match.Index);
                builder.Append(ReplaceChar, spaceNum); //<icon xxx />替换为空格
                builder.Append(richText, match.Index + match.Length, richText.Length - match.Index - match.Length);
                richText = builder.ToString();
            }

            // Populate charaters
            cachedTextGenerator.Populate(richText, settings);
            textGenVerts = cachedTextGenerator.verts;
            // Last 4 verts are always a new line...
            int vertCount = textGenVerts.Count - 4;

            // Image wrap check
            for (int i = 0; i < m_Icons.Count; i++)
            {
                IconInfo iconInfo = m_Icons[i];
                int vertStart = iconInfo.vertStart;
                int vertNum = iconInfo.vertNum;
                int maxVertice = Mathf.Min(vertStart + vertNum, vertCount);
                if (horizontalOverflow == HorizontalWrapMode.Wrap &&
                    textGenVerts[maxVertice - 2].position.x * unitsPerPixel > rectTransform.rect.xMax)
                {
                    // New line
                    richText = richText.Insert(vertStart / 4, "\r\n");
                    for (int j = i; j < m_Icons.Count; j++)
                    {
                        m_Icons[j].vertStart += 8; //+2个四边形
                    }
                    cachedTextGenerator.Populate(richText, settings);
                    textGenVerts = cachedTextGenerator.verts;
                    vertCount = textGenVerts.Count - 4;
                }
            }

            PrintTextGenInfo(richText, cachedTextGenerator);
            Debug.Log($"iconsCount: {m_Icons.Count}, vertCount:{vertCount}, {richText.Length}, {richText}");

            // Image position calculation
            for (int i = m_Icons.Count - 1; i >= 0; i--)
            {
                IconInfo iconInfo = m_Icons[i];
                int vertStart = iconInfo.vertStart;
                if (vertStart < vertCount)
                {
                    var offset = 0f;
                    switch (iconInfo.align)
                    {
                    case "top":
                        offset = (fontSize2 - iconInfo.size.y) * 0.5f + fontSize2;
                        break;
                    case "bottom":
                        offset = (iconInfo.size.y - fontSize2) * 0.5f;
                        break;
                    default:
                        offset = fontSize2 * 0.5f;
                        break;
                    }
                    UIVertex vert = textGenVerts[vertStart];
                    Vector2 vertex = vert.position;
                    vertex *= unitsPerPixel;
                    vertex.x += rectTransform.sizeDelta.x * (rectTransform.pivot.x - 0.5f) + iconInfo.size.x * 0.5f;
                    vertex.y += rectTransform.sizeDelta.y * (rectTransform.pivot.y - 0.5f) + offset;
                    iconInfo.anchoredPos = vertex;
                    iconInfo.color = Color.white;

                    if (!string.IsNullOrEmpty(iconInfo.e))
                    {
                        Event e = new Event();
                        e.name = iconInfo.e;
                        e.args = iconInfo.args;
                        e.rect = new Rect(
                            vert.position.x * unitsPerPixel,
                            vert.position.y * unitsPerPixel + offset - iconInfo.size.y * 0.5f,
                            iconInfo.size.x,
                            iconInfo.size.y
                        );
                        m_EventList.Add(e);
                    }
                }
                else
                {
                    m_Icons.RemoveAt(i);
                }
            }

            // Mark need re-layout image
            m_IconLayoutDirty = true;

            return richText;
        }

        private void Update()
        {
            if (m_IconLayoutDirty)
            {
                m_IconLayoutDirty = false;
                RebuildIconLayout();
            }
        }

        private void RebuildIconLayout()
        {
            m_ImageList.RemoveAll(image => image == null);

            if (m_ImageList.Count == 0)
                GetComponentsInChildren(true, m_ImageList);

            for (int i = m_ImageList.Count; i < m_Icons.Count; i++)
            {
                m_ImageList.Add(NewImage());
            }

            for (int i = 0; i < m_Icons.Count; i++)
            {
                var spriteName = m_Icons[i].name;
                var position = m_Icons[i].anchoredPos;
                var size = m_Icons[i].size;
                var still = m_Icons[i].still;
                var color = m_Icons[i].color;
                var img = m_ImageList[i];

                var exist = !string.IsNullOrEmpty(spriteName) && m_SpriteList.ContainsKey(spriteName);
                img.sprite = exist ? m_SpriteList[spriteName] : null;
                img.enabled = exist || still;
                img.color = color;
                img.rectTransform.anchoredPosition = position;
                img.rectTransform.sizeDelta = size;
            }

            //多出来的Image
            for (int i = m_Icons.Count; i < m_ImageList.Count; i++)
            {
                m_ImageList[i].sprite = null;
                m_ImageList[i].enabled = false;
            }
        }

        private Image NewImage()
        {
            var resources = new DefaultControls.Resources();
            var go = DefaultControls.CreateImage(resources);
            if (Application.isPlaying) GameObject.DontDestroyOnLoad(go);
            go.layer = gameObject.layer;
            var rt = go.transform as RectTransform;
            if (rt)
            {
                rt.SetParent(rectTransform);
                rt.localPosition = Vector3.zero;
                rt.localRotation = Quaternion.identity;
                rt.localScale = Vector3.one;
            }
            Image img = go.GetComponent<Image>();
            img.raycastTarget = false;
            return img;
        }



        //阴影
        private void ApplyShadowEffect(Shadow tag, IList<UIVertex> verts)
        {
            int start = tag.tagContentStartIndex * 4;
            int end = Mathf.Min(tag.tagContentEndIndex * 4 + 4, verts.Count);
            UIVertex vt;
            for (int i = start; i < end; i++)
            {
                vt = verts[i];
                verts.Add(vt);
                Vector3 v = vt.position;
                v.x += tag.x;
                v.y += tag.y;
                vt.position = v;
                var newColor = tag.c;
                newColor.a = (newColor.a * verts[i].color.a) / 255;
                vt.color = newColor;
                verts[i] = vt;
            }
        }

        //描边
        private void ApplyOutlineEffect(Outline tag, IList<UIVertex> verts)
        {
            int start = tag.tagContentStartIndex * 4;
            int end = Mathf.Min(tag.tagContentEndIndex * 4 + 4, verts.Count);
            UIVertex vt;
            for (int x = -1; x <= 1; x += 2)
            {
                for (int y = -1; y <= 1; y += 2)
                {
                    for (int i = start; i < end; i++)
                    {
                        vt = verts[i];
                        Vector3 v = vt.position;
                        v.x += tag.x * x;
                        v.y += tag.y * y;
                        vt.position = v;
                        var newColor = tag.c;
                        newColor.a = (newColor.a * verts[i].color.a) / 255;
                        vt.color = newColor;
                        verts.Add(vt);
                    }
                }
            }
            for (int i = start; i < end; i++)
            {
                verts.Add(verts[i]);
            }
        }

        //渐变
        private void ApplyGradientEffect(GradientL tag, IList<UIVertex> verts)
        {
            int start = tag.tagContentStartIndex * 4;
            int end = Mathf.Min(tag.tagContentEndIndex * 4 + 4, verts.Count);
            float min = float.MaxValue;
            float max = float.MinValue;
            Vector2 dir = new Vector2(tag.x, tag.y);
            for (int i = start; i < end; i++)
            {
                float dot = Vector3.Dot(verts[i].position, dir);
                if (dot > max) max = dot;
                else if (dot < min) min = dot;
            }
            float h = max - min;
            UIVertex vt;
            for (int i = start; i < end; i++)
            {
                vt = verts[i];
                vt.color = Color32.Lerp(tag.from, tag.to, (Vector3.Dot(vt.position, dir) - min) / h);
                verts[i] = vt;
            }
        }

        private void ApplyUnderlineEffect(Underline tag, IList<UIVertex> textGenVerts)
        {
            float fontSizeHalf = fontSize * 0.5f;
            float unitsPerPixel = 1 / pixelsPerUnit;

            int start = tag.tagContentStartIndex * 4;
            int end = Mathf.Min(tag.tagContentEndIndex * 4 + 4, textGenVerts.Count);

            UIVertex vert_lb = textGenVerts[start + 3]; //顺序: lt, rt, rb, lb
            Debug.Log($"({textGenVerts[start].position.x * unitsPerPixel},{textGenVerts[start].position.y * unitsPerPixel}), ({textGenVerts[start + 1].position.x * unitsPerPixel}, {textGenVerts[start + 1].position.y * unitsPerPixel}), {textGenVerts[start + 2].position.y}, {textGenVerts[start + 3].position.y}");
            UIVertex vert_rb;
            float minY = vert_lb.position.y;
            float maxY = textGenVerts[start].position.y;
            for (int i = start + 2; i <= end - 2; i += 4) //逐个字符检查, 每一行用一个underline的
            {
                vert_rb = textGenVerts[i];
                bool newline = Mathf.Abs(vert_rb.position.y - vert_lb.position.y) > fontSizeHalf;
                Debug.Log($"quad_{i / 4}, vert_{i - (i / 4) * 4}: vt2:{vert_rb.position.y}, vt1:{vert_lb.position.y}");
                if (newline
                    || i == end - 2 //比如只有一行的情况, 最后一个字符时, 是下划线的尾端
                )
                {
                    IconInfo iconInfo = new IconInfo(); //icon显示成直线
                    iconInfo.still = true;
                    int tailIndex = !newline && (i == end - 2) ? i : i - 4; //结束字符上时, 使用前一个字符的y值
                    vert_rb = textGenVerts[tailIndex];
                    minY = Mathf.Min(minY, vert_rb.position.y); //字符中, y值最低的
                    maxY = Mathf.Max(maxY, textGenVerts[tailIndex - 1].position.y); //字符中y值最高的
                    iconInfo.size = new Vector2((vert_rb.position.x - vert_lb.position.x) * unitsPerPixel, tag.h);

                    //Image矩形的左下角, 对齐下划线左下角就对了
                    Vector2 anchoredPos = new Vector2(vert_lb.position.x, minY);
                    anchoredPos *= unitsPerPixel;
                    anchoredPos += new Vector2(iconInfo.size.x * 0.5f, -tag.h * 0.5f);
                    anchoredPos += new Vector2(rectTransform.sizeDelta.x * (rectTransform.pivot.x - 0.5f), rectTransform.sizeDelta.y * (rectTransform.pivot.y - 0.5f)); //vert是相对parent的pivot的
                    iconInfo.anchoredPos = anchoredPos;
                    iconInfo.color = (tag.c == Color.white) ? color : tag.c;
                    m_Icons.Add(iconInfo);

                    if (!string.IsNullOrEmpty(tag.e))
                    {
                        Event e = new Event();
                        e.name = tag.e;
                        e.args = tag.args;
                        e.rect = new Rect(
                            vert_lb.position.x * unitsPerPixel,
                            minY * unitsPerPixel,

                            iconInfo.size.x,
                            (maxY - minY) * unitsPerPixel
                        );
                        m_EventList.Add(e);
                    }

                    vert_lb = textGenVerts[i + 1];
                    minY = vert_lb.position.y;
                    if (newline && i == end - 2) i -= 4;
                }
                else
                {
                    minY = Mathf.Min(minY, vert_rb.position.y);
                    maxY = Mathf.Max(maxY, textGenVerts[i - 1].position.y);
                }
            }
        }

        //下划线文本点击
        public void OnPointerClick(PointerEventData eventData)
        {
            Vector2 lp;
            RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTransform, eventData.position, eventData.pressEventCamera, out lp);
            for (int i = m_EventList.Count - 1; i >= 0; i--)
            {
                Event e = m_EventList[i];
                if (e.rect.Contains(lp))
                {
                    clickHandler.Invoke(e.name, e.args);
                    break;
                }
            }
        }




        //文本解析

        private class TextInterpreter
        {
            private static readonly Regex TagReg = new Regex(@"</*material[^>]*>");
            private const string TagSuffix = "</material>";

            private List<Tag> close;
            private Stack<InterpretInfo> open;

            public TextInterpreter()
            {
                close = new List<Tag>();
                open = new Stack<InterpretInfo>();
            }

            public void Parse(string richText, out List<Tag> tags)
            {
                close.Clear();
                open.Clear();

                Match match = TagReg.Match(richText);
                while (match.Success)
                {
                    if (match.Value == TagSuffix)
                    {
                        if (open.Count > 0)
                        {
                            InterpretInfo iinfo = open.Pop();
                            iinfo.tagContentEndIndex = match.Index - 1;
                            if (iinfo.tagContentEndIndex >= iinfo.tagContentStartIndex)
                            {
                                Debug.Log($"ToTag: {iinfo.tagBeginStr}, {richText.Substring(iinfo.tagContentStartIndex, iinfo.tagContentEndIndex - iinfo.tagContentStartIndex)}");
                                Tag tag = iinfo.ToTag();
                                if (tag != null) close.Add(tag);
                            }
                        }
                    }
                    else
                    {
                        InterpretInfo iinfo = new InterpretInfo();
                        iinfo.tagBeginStr = match.Value;
                        iinfo.tagContentStartIndex = match.Index + match.Length;
                        open.Push(iinfo);
                    }
                    match = match.NextMatch();
                }
                tags = close;
            }
        }

        private class InterpretInfo
        {
            private static readonly Regex TagReg = new Regex(@"<material=([^>\s]+)([^>]*)>");
            private static readonly Regex ItemReg = new Regex(@"(\w+)=([^\s]+)");
            public string tagBeginStr;

            public int tagContentStartIndex;
            public int tagContentEndIndex;

            public Tag ToTag()
            {
                Tag tag = null;
                Match match = TagReg.Match(tagBeginStr);
                if (match.Success)
                {
                    /*
                    Debug.Log($"===== match: itemValue:{match.Value}, index:{match.Index}, len:{match.Length}, groupsCount:{match.Groups.Count}");
                    for (var j = 0; j < match.Groups.Count; ++j)
                    {
                        Group g = match.Groups[j];
                        Debug.Log($"group{j}: v:{g.Value}, index:{g.Index}, len:{g.Length}, succ:{g.Success}");
                    }
                    */

                    string type = match.Groups[1].Value;
                    if (!type.StartsWith("#"))
                    {
                        var values = ItemReg.Matches(match.Groups[2].Value);
                        switch (type)
                        {
                        case "shadow":
                        {
                            tag = new Shadow();
                            break;
                        }
                        case "outline":
                        {
                            tag = new Outline();
                            break;
                        }
                        case "gradient":
                        {
                            tag = new GradientL();
                            break;
                        }
                        case "underline":
                        {
                            tag = new Underline();
                            break;
                        }
                        }

                        if (tag != null)
                        {
                            tag.tagContentStartIndex = tagContentStartIndex;
                            tag.tagContentEndIndex = tagContentEndIndex;
                            for (int i = 0; i < values.Count; i++)
                            {
                                string name = values[i].Groups[1].Value;
                                string value = values[i].Groups[2].Value;
                                tag.SetValue(name, value);
                            }
                        }
                    }
                }
                return tag;
            }
        }

        private enum TagType
        {
            None,
            Shadow,
            Outline,
            Gradient,
            Underline,
        }

        private abstract class Tag
        {
            public int tagContentStartIndex; //在整个字符串中的开始
            public int tagContentEndIndex;

            public virtual TagType type
            {
                get
                {
                    return TagType.None;
                }
            }
            public virtual void SetValue(string name, string value)
            {

            }
        }



        private class Shadow : Tag
        {
            public Color c = Color.black;
            public float x = 1;
            public float y = -1;
            public override TagType type
            {
                get
                {
                    return TagType.Shadow;
                }
            }
            public override void SetValue(string name, string value)
            {
                base.SetValue(name, value);
                switch (name)
                {
                case "c":
                {
                    ColorUtility.TryParseHtmlString(value, out c);
                    break;
                }
                case "x":
                {
                    float.TryParse(value, out x);
                    break;
                }
                case "y":
                {
                    float.TryParse(value, out y);
                    break;
                }
                }
            }
        }

        private class Outline : Shadow
        {
            public override TagType type
            {
                get
                {
                    return TagType.Outline;
                }
            }
        }

        private class GradientL : Tag
        {
            public Color from = Color.white;
            public Color to = Color.black;
            public float x = 0;
            public float y = -1;
            public override TagType type
            {
                get
                {
                    return TagType.Gradient;
                }
            }
            public override void SetValue(string name, string value)
            {
                base.SetValue(name, value);
                switch (name)
                {
                case "from":
                {
                    ColorUtility.TryParseHtmlString(value, out from);
                    break;
                }
                case "to":
                {
                    ColorUtility.TryParseHtmlString(value, out to);
                    break;
                }
                case "x":
                {
                    float.TryParse(value, out x);
                    break;
                }
                case "y":
                {
                    float.TryParse(value, out y);
                    break;
                }
                }
            }
        }

        private class Underline : Tag
        {
            public Color c = Color.white;
            public float h = 1.5f;
            public string e;
            public string args;
            public override TagType type
            {
                get
                {
                    return TagType.Underline;
                }
            }
            public override void SetValue(string name, string value)
            {
                base.SetValue(name, value);
                switch (name)
                {
                case "c":
                {
                    ColorUtility.TryParseHtmlString(value, out c);
                    break;
                }
                case "h":
                {
                    float.TryParse(value, out h);
                    break;
                }
                case "event":
                {
                    e = value;
                    break;
                }
                case "args":
                {
                    args = value;
                    break;
                }
                }
            }
        }

    }


}
